package com.systar.activity.ui;

import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.joda.time.Duration;
import org.joda.time.Instant;

import com.google.common.base.Strings;
import com.systar.activity.IActivable;
import com.systar.activity.events.IActivityEvent;
import com.systar.activity.events.ParsingActivityEvent;
import com.systar.activity.events.StatusActivityEvent;
import com.systar.activity.monitor.ActivityManager;
import com.systar.activity.monitor.ActivitySource;

public class ActivityDialog extends ApplicationWindow
{
	private Table sourceTable;
	private Table errorTable;
	private Instant sourceActivityStart;
	private Duration activityDuration;
	private int activityCount;
	private final ScheduledExecutorService refreshExecutor;
	private final ActivityManager manager;
	private Color activeColor;
	private Color idleColor;
	private AnimatedActionButton onlineImage;

	private volatile boolean closing = false;

	public ActivityDialog(Shell parentShell, ActivityManager manager)
	{
		super(parentShell);

		this.manager = manager;

		sourceActivityStart = null;
		activityDuration = new Duration(0);
		activityCount = 0;

		// refresh
		refreshExecutor = Executors.newSingleThreadScheduledExecutor();
		addToolBar(SWT.NONE);
        addStatusLine();
	}

	@Override
	public int open()
	{
		final boolean AUTO_START = true;
		if (AUTO_START)
		{
			start();
		}
		return super.open();
	}

	private void start() 
	{
		// start refresh thread
		Runnable refresh = new Runnable()
		{

			@Override
			public void run()
			{
				System.out.println("Entering Refresh.run");
				try
				{
					if (closing)
						return;
	
					// refresh events data
					List<IActivityEvent> events = manager.getDisplayEvents();
					if (events.isEmpty())
						return;
					System.out.println("dispatching events to UI");
	
					for (IActivityEvent e : events)
					{
						if (closing)
							return;
						String type = e.getType();
						if (StatusActivityEvent.TYPE.equals(type))
						{
							StatusActivityEvent event = (StatusActivityEvent) e;
							String src = event.getSourceName();
							safeUpdateRow(src, event.isActive());
						}
						else if (ParsingActivityEvent.TYPE.equals(type))
						{
							ParsingActivityEvent event = (ParsingActivityEvent) e;
							String src = event.getSourceName();
							safeUpdateRow(src, event.getNbInfo(), event.getNbWarn(), event.getNbErr(), event.getMessage());
						}
					}
	
					// refresh animated UI
					safeUpdateUI();
				}
				finally
				{
					System.out.println("Leaving Refresh.run");
				}
			}

		};
		refreshExecutor.scheduleWithFixedDelay(refresh, 500, 500, TimeUnit.MILLISECONDS);
		try
		{
			// pause to let working threads start first
			Thread.sleep(1);
		}
		catch (InterruptedException e)
		{
			// skip
		}
		manager.start();
	}

	@Override
	protected void configureShell(Shell shell)
	{
		super.configureShell(shell);
		final Image image = new Image(shell.getDisplay(), getClass().getResourceAsStream("settings.png"));
		shell.setImage(image);
		activeColor = shell.getDisplay().getSystemColor(SWT.COLOR_RED);
		idleColor = shell.getDisplay().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
	}

	@Override
	protected Point getInitialSize()
	{
		return new Point(600, 600);
	}

	@Override
	protected Control createContents(Composite parent)
	{
		getShell().setText("Activity Monitor");

		SashForm sashForm1 = new SashForm(parent, SWT.VERTICAL);
		createSourceArea(sashForm1);
		createErrorArea(sashForm1);
		sashForm1.setWeights(new int[] { 2, 3 });

		return sashForm1;
	}

	private void createSourceArea(Composite parent)
	{
		// Create a table displaying sources and their status
		sourceTable = new Table(parent, SWT.VIRTUAL | SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
		sourceTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

		TableColumn tc = new TableColumn(sourceTable, SWT.LEFT);
		tc.setWidth(150);
		tc.setResizable(false);
		tc.setText("Data Source name");

		tc = new TableColumn(sourceTable, SWT.LEFT);
		tc.setWidth(100);
		tc.setResizable(false);
		tc.setText("Activity");

		tc = new TableColumn(sourceTable, SWT.NONE);
		tc.setWidth(100);
		tc.setResizable(false);
		tc.setText("Infos");

		tc = new TableColumn(sourceTable, SWT.NONE);
		tc.setWidth(100);
		tc.setResizable(false);
		tc.setText("Warnings");

		tc = new TableColumn(sourceTable, SWT.NONE);
		tc.setWidth(100);
		tc.setResizable(false);
		tc.setText("Errors");

		sourceTable.setHeaderVisible(true);
		sourceTable.setLinesVisible(false);

		for (ActivitySource source : manager.getSources())
		{
			// insert rows
			TableItem row = new TableItem(sourceTable, SWT.NONE);
			row.setText(0, source.getName());
			updateRow(row, false);
			updateRow(row, 0, 0, 0);
		}

		// increase row height
		sourceTable.addListener(SWT.MeasureItem, new Listener()
		{
			@Override
			public void handleEvent(Event event)
			{
				event.height = event.gc.getFontMetrics().getHeight() * 4;
			}
		});
	}

	@Override
	protected ToolBarManager createToolBarManager(int style)
	{
		ToolBarManager toolBarManager = new ToolBarManager(style);

		// EXIT button
		ActionContributionItem exitButton = new ActionContributionItem(new Action("Exit", ImageDescriptor.createFromFile(getClass(), "exit.jpg"))
		{
			@Override
			public void run()
			{
				quit();
			}
		});
		exitButton.setMode(ActionContributionItem.MODE_FORCE_TEXT);
		toolBarManager.add(exitButton);

		toolBarManager.add(new Separator());

		// SERVER button
		onlineImage = new AnimatedActionButton(getClass().getResourceAsStream("parabol_a.gif"), getClass().getResourceAsStream("parabol.gif"));
		ActionContributionItem onlineButton = new ActionContributionItem(new Action("Server", onlineImage)
		{
			@Override
			public void run()
			{
				/* toggle online notifier */
				IActivable onlineNotifier = manager.getOnlineNotifier();
				if (onlineNotifier.isActive())
				{
					onlineNotifier.deactivate();
				}
				else
				{
					onlineNotifier.activate();
					if (!onlineNotifier.isActive())
					{
						getStatusLineManager().setErrorMessage("Could not contact Activity Reporter servlet");
					}
				}
				onlineImage.setAnimated(onlineNotifier.isActive());
			}
		});
		onlineButton.setMode(ActionContributionItem.MODE_FORCE_TEXT);
		toolBarManager.add(onlineButton);
		onlineImage.setAction(onlineButton);
 
		return toolBarManager;
	}

	private static void updateRow(TableItem row, int index, String key, int value)
	{
		Integer nbInfoStored = (Integer) row.getData(key);
		if (nbInfoStored == null)
			nbInfoStored = new Integer(0);
		nbInfoStored += value;
		row.setData(key, nbInfoStored);
		row.setText(index, nbInfoStored.toString());
	}

	private static void updateRow(TableItem row, int nbInfo, int nbWarn, int nbErr)
	{
		updateRow(row, 2, "nbInfo", nbInfo);
		updateRow(row, 3, "nbWarn", nbWarn);
		updateRow(row, 4, "nbErr", nbErr);
	}

	private void updateRow(TableItem row, boolean active)
	{
		row.setText(1, active ? "ON" : "OFF");
		row.setBackground(active ? activeColor : idleColor);
	}

	private TableItem getRowByName(String name)
	{
		for (TableItem row : sourceTable.getItems())
		{
			if (name.equals(row.getText(0)))
			{
				return row;
			}
		}
		return null;
	}

	private void safeUpdateRow(final String name, final boolean active)
	{
		getShell().getDisplay().asyncExec(new Runnable()
		{
			@Override
			public void run()
			{
				// update duration
				if (active)
				{
					if (activityCount == 0)
					{
						sourceActivityStart = Instant.now();
						System.out.println(name + " start on " + sourceActivityStart);
					}
					activityCount++;
				}
				else
				{
					activityCount--;
					if (activityCount == 0)
					{
						Duration duration = new Duration(sourceActivityStart, Instant.now());
						activityDuration = activityDuration.plus(duration);
						System.out.println(name + " duration: " + duration);
					}
				}
				if (activityDuration.isLongerThan(Duration.ZERO))
					getStatusLineManager().setMessage(String.format("Activity duration: %d ms", activityDuration.getMillis()));

				// update table
				TableItem row = getRowByName(name);
				if (row == null)
					return;
				updateRow(row, active);
			}
		});
	}

	private void updateErrorRow(final String name, final String message)
	{
		if (Strings.isNullOrEmpty(message))
			return;

		StringTokenizer stringTokenizer = new StringTokenizer(message, "\r\n");

		while (stringTokenizer.hasMoreTokens())
		{
			TableItem row = new TableItem(errorTable, SWT.NONE);
			row.setText(0, name);
			row.setText(1, stringTokenizer.nextToken());
		}
	}

	private void safeUpdateRow(final String name, final int nbInfo, final int nbWarn, final int nbErr, final String message)
	{
		getShell().getDisplay().asyncExec(new Runnable()
		{
			@Override
			public void run()
			{
				TableItem row = getRowByName(name);
				if (row == null)
					return;
				updateRow(row, nbInfo, nbWarn, nbErr);
				updateErrorRow(name, message);
			}
		});
	}

	private void safeUpdateUI()
	{
		getShell().getDisplay().asyncExec(new Runnable()
		{
			@Override
			public void run()
			{
				IActivable onlineNotifier = manager.getOnlineNotifier();
				boolean isOnline = (onlineNotifier != null) && onlineNotifier.isActive();
				onlineImage.setAnimated(isOnline);
			}
		});
	}

	private void createErrorArea(Composite parent)
	{
		errorTable = new Table(parent, SWT.VIRTUAL | SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
		errorTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

		TableColumn tc = new TableColumn(errorTable, SWT.LEFT);
		tc.setWidth(150);
		tc.setResizable(false);
		tc.setText("Data Source name");

		tc = new TableColumn(errorTable, SWT.LEFT);
		tc.setWidth(400);
		tc.setResizable(false);
		tc.setText("Error message");

		errorTable.setHeaderVisible(true);
		errorTable.setLinesVisible(true);

	}

	public boolean quit()
	{
		Display.getDefault().syncExec(new Runnable()
		{
			@Override
			public void run()
			{
				handleShellCloseEvent();
			}
		});

		return true;
	}

	@Override
	protected void handleShellCloseEvent()
	{
		closing = true;
		refreshExecutor.shutdown();
		manager.shutdown();
		while (getShell().getDisplay().readAndDispatch())
		{
			// pump remaining events
		}
		super.handleShellCloseEvent();
	}
}
